package com.wzdigit.wms.wms.service.transfer;

import com.alibaba.fastjson.JSON;
import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.github.pagehelper.Page;
import com.github.pagehelper.PageHelper;
import com.wzdigit.framework.Shift;
import com.wzdigit.mes.basic.api.BasicSimpleCodeRuleApi;
import com.wzdigit.wms.basic.client.Pager;
import com.wzdigit.wms.basic.client.PagerInfo;
import com.wzdigit.wms.basic.client.request.transfer.CreateRemainDocRequest;
import com.wzdigit.wms.basic.client.response.Result;
import com.wzdigit.wms.basic.client.response.mid.IfShipnoticeHeaderResponse;
import com.wzdigit.wms.basic.client.response.transfer.CalculateRemainResponse;
import com.wzdigit.wms.basic.client.response.transfer.CalculateRemainResponse.CalculateRemainItemResponse;
import com.wzdigit.wms.basic.client.response.transfer.GdnDtlForIfListResponse;
import com.wzdigit.wms.basic.client.response.transfer.GdnDtlRemainPcResponse;
import com.wzdigit.wms.basic.domain.basic.Location;
import com.wzdigit.wms.basic.domain.basic.Sku;
import com.wzdigit.wms.basic.domain.basic.Warehouse;
import com.wzdigit.wms.basic.domain.dtos.inventory.UpdateInventoryInputDto;
import com.wzdigit.wms.basic.domain.dtos.order.QtyGroupPoSkuDto;
import com.wzdigit.wms.basic.domain.dtos.transfer.PicktaskDtlGoodsBySourceNo;
import com.wzdigit.wms.basic.domain.dtos.transfer.UpdateGdnDtlQtyDto;
import com.wzdigit.wms.basic.domain.inventory.Goods;
import com.wzdigit.wms.basic.domain.inventory.GoodsLog;
import com.wzdigit.wms.basic.domain.transfer.*;
import com.wzdigit.wms.basic.enums.*;
import com.wzdigit.wms.basic.enums.log.GoodsLogEnum;
import com.wzdigit.wms.basic.mapper.core.basic.SkuMapper;
import com.wzdigit.wms.basic.mapper.core.basic.WarehouseMapper;
import com.wzdigit.wms.basic.mapper.core.inventory.GoodsMapper;
import com.wzdigit.wms.basic.mapper.core.mid.IfShipnoticeHeaderMapper;
import com.wzdigit.wms.basic.mapper.core.transfer.*;
import com.wzdigit.wms.basic.util.HttpSessionUtil;
import com.wzdigit.wms.basic.util.common.CommonString;
import com.wzdigit.wms.basic.util.common.FileUrlString;
import com.wzdigit.wms.basic.util.common.SeparatorString;
import com.wzdigit.wms.wms.service.basic.FileService;
import com.wzdigit.wms.wms.service.basic.LocationService;
import com.wzdigit.wms.wms.service.core.GoodsService;
import com.wzdigit.wms.basic.service.code.InventoryService;
import lombok.extern.slf4j.Slf4j;
import net.dreamlu.mica.core.utils.BeanUtil;
import net.sf.jasperreports.engine.JasperFillManager;
import net.sf.jasperreports.engine.JasperPrint;
import net.sf.jasperreports.engine.JasperReport;
import net.sf.jasperreports.engine.export.ooxml.JRXlsxExporter;
import net.sf.jasperreports.engine.util.JRLoader;
import net.sf.jasperreports.export.*;
import org.apache.commons.lang.StringUtils;
import org.apache.dubbo.config.annotation.Reference;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.core.io.ClassPathResource;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;

import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import javax.sql.DataSource;
import java.io.InputStream;
import java.io.OutputStream;
import java.math.BigDecimal;
import java.sql.Connection;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 发货余留单单-服务类
 * 
 * @author yangdongjie
 * @GdnService.java
 * @2021年2月24日下午3:25:03
 */
@Service
@Slf4j
public class GdnRemainService {

	@Autowired
	private FileService fileService;

    @Autowired
    private InventoryService inventoryService;

    @Autowired
    private GdnDtlGoodsMapper gdnDtlGoodsMapper;

    @Autowired
    private PicktaskDtlGoodsMapper picktaskDtlGoodsMapper;

    @Autowired
    private GdnRemainDtlGoodsMapper gdnRemainDtlGoodsMapper;

	@Autowired
	private IfShipnoticeHeaderMapper ifShipnoticeHeaderMapper;

	@Autowired
	private GdnRemainHeaderMapper gdnRemainHeaderMapper;
	
	@Autowired
	private GdnRemainDtlMapper gdnRemainDtlMapper;
	
	@Autowired
	private GdnHeaderMapper gdnHeaderMapper;
	
	@Autowired
	private GdnDtlMapper gdnDtlMapper;
	
	@Autowired
	private GoodsMapper goodsMapper;
	
	@Autowired
	private SkuMapper skuMapper;
	
	@Autowired
	private WarehouseMapper warehouseMapper;
	
	@Autowired
	private LocationService locationService;
	
	@Autowired
	private GoodsService goodsService;
	
	@Autowired
	private DataSource dataSource;
	
	@Reference(version = "${dubbo.consumers.basic.simple.code.rule.api:1.0.0}", check = false)
    private BasicSimpleCodeRuleApi basicSimpleCodeRuleApi;
	
	private static final String REMAINNO = "REMAINNO";

	/**
	 * 根据来源单号获取发货通知单
	 *
	 * @return
	 */
	public PagerInfo<List<IfShipnoticeHeaderResponse>> selectIfShipnoticeHeaderByNoticeNo(String filter, Pager<?> pager) {
		Page<?> page = PageHelper.offsetPage(pager.getSkipCount(), pager.getMaxResultCount());
		PagerInfo<List<IfShipnoticeHeaderResponse>> pagerInfo = new PagerInfo<List<IfShipnoticeHeaderResponse>>();
		List<IfShipnoticeHeaderResponse> list = ifShipnoticeHeaderMapper.selectByNoticeNo(filter);
		pagerInfo.setTotalCount(page.getTotal());
		pagerInfo.setItems(list);
		return pagerInfo;
	}
	
	/**
	 * 根据来源单号获取发货单明细
	 * 
	 * @param noticeNo
	 * @param pager
	 * @return
	 */
	public PagerInfo<List<GdnDtlForIfListResponse>> selectGdnDtlForIfListByNoticeNo(String noticeNo, Pager<?> pager) {
		Page<?> page = PageHelper.offsetPage(pager.getSkipCount(), pager.getMaxResultCount());
		PagerInfo<List<GdnDtlForIfListResponse>> pagerInfo = new PagerInfo<List<GdnDtlForIfListResponse>>();
		List<GdnDtlForIfListResponse> list = gdnDtlMapper.selectListBySourceNo(noticeNo);
		pagerInfo.setTotalCount(page.getTotal());
		pagerInfo.setItems(list);
		return pagerInfo;
	}

	/**
	 * 根据来源单号/发货单明细获取余留信息
	 * 
	 * @param gdnDtlId
	 * @param noticeNo
	 * @param pager
	 * @return
	 */
	public PagerInfo<List<GdnDtlRemainPcResponse>> selectGdnDtlRemainListByGdnDtlIdOrSourceNo(Integer gdnDtlId, 
			String noticeNo, Pager<?> pager) {
		Page<?> page = PageHelper.offsetPage(pager.getSkipCount(), pager.getMaxResultCount());
		PagerInfo<List<GdnDtlRemainPcResponse>> pagerInfo = new PagerInfo<List<GdnDtlRemainPcResponse>>();
		List<GdnDtlRemainPcResponse> list = gdnRemainDtlMapper.selectAllByGdnDtlIdOrSourceNo(gdnDtlId, noticeNo);
		pagerInfo.setTotalCount(page.getTotal());
		pagerInfo.setItems(list);
		return pagerInfo;
	}
	
	/**
	 * 打印余留单pdf
	 * @param noticeNo
	 * @param servletRequest
	 * @param response
	 * @throws Exception
	 * 
	 */
	@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
	public void loadingRemainPrint(String noticeNo,
			HttpServletRequest servletRequest, HttpServletResponse response) throws Exception {
		InputStream inputStream = null;
		OutputStream outputStream = null;
		try {
			int printFlag = PrintFlagEnum.YES.getCode();
			String loginName = HttpSessionUtil.getLoginName(servletRequest);
			
			// 发货单表头
			QueryWrapper<GdnHeader> gdnHeaderQueryWrapper = new QueryWrapper<GdnHeader>();
			gdnHeaderQueryWrapper.eq("SOURCE_SHIP_NOTICE", noticeNo);
			List<GdnHeader> gdnHeaderList = gdnHeaderMapper.selectList(gdnHeaderQueryWrapper);
			List<Long> gdnHeaderIdList = gdnHeaderList.stream().map(GdnHeader::getGdnHeaderId).collect(Collectors.toList());
			gdnHeaderMapper.updatePrint(null, printFlag, loginName, gdnHeaderIdList);
			
			// 装柜余留单表头
			QueryWrapper<GdnRemainHeader> gdnRemainHeaderQueryWrapper = new QueryWrapper<GdnRemainHeader>();
			gdnRemainHeaderQueryWrapper.in("GDN_HEADER_ID", gdnHeaderIdList);
			List<GdnRemainHeader> gdnRemainHeaderList = gdnRemainHeaderMapper.selectList(gdnRemainHeaderQueryWrapper);
			List<Integer> gdnRemainHeaderIdList = gdnRemainHeaderList.stream().map(GdnRemainHeader::getGdnRemainHeaderId).collect(Collectors.toList());
			gdnRemainHeaderMapper.updatePrint(printFlag, loginName, gdnRemainHeaderIdList);
			
			// 获取文件流
			ClassPathResource resource = new ClassPathResource(FileUrlString.LOADING_REMAIN_PRINT);
			inputStream = resource.getInputStream();
			JasperReport jasperReport = (JasperReport) JRLoader.loadObject(inputStream);
			
			// 入参
			Map<String, Object> parameters = new HashMap<String, Object>(1);
			List<ExporterInputItem> items = new ArrayList<ExporterInputItem>(gdnRemainHeaderList.size());
			for (GdnRemainHeader gdnRemainHeader : gdnRemainHeaderList) {
				parameters.clear();
				parameters.put("GDN_REMAIN_HEADER_ID", gdnRemainHeader.getGdnRemainHeaderId());
				JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, dataSource.getConnection());
				
				// 设置pdf尺寸
				jasperPrint.setPageWidth(595);
				jasperPrint.setPageHeight(396);
				jasperPrint.setLeftMargin(0);
				jasperPrint.setRightMargin(0);
				jasperPrint.setTopMargin(0);
				jasperPrint.setBottomMargin(0);
				items.add(new SimpleExporterInputItem(jasperPrint));
			}
			
			String pdfName = new String("装柜余留单".getBytes(), "iso8859-1") + ".xlsx";
			response.setContentType("application/xls");   
			response.setCharacterEncoding("UTF-8"); 
			response.setHeader("Content-Disposition", "attachment; filename=" + pdfName);
			outputStream = response.getOutputStream();
			
			// Xls格式的导出器 JRXlsAbstractExport
			JRXlsxExporter exporter = new JRXlsxExporter();
			
			//设置输入项   
			ExporterInput exporterInput = new SimpleExporterInput(items);
			exporter.setExporterInput(exporterInput);
			
			//设置输出项   
			OutputStreamExporterOutput exporterOutput = new SimpleOutputStreamExporterOutput(outputStream);   
			exporter.setExporterOutput(exporterOutput);
			// 导出
			exporter.exportReport();
		} catch (Exception e){
			e.printStackTrace();
			throw e;
		} finally {
			if(outputStream != null) {
				outputStream.close();
			}
			if(inputStream != null) {
				inputStream.close();
			}
		}
	}

	/**
	 * 打印余留单pdf
	 * @param noticeNo
	 * @param servletRequest
	 * @param response
	 * @throws Exception
	 *
	 */
	@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
	public Result loadingRemainPrintNew(String noticeNo,
								   HttpServletRequest servletRequest, HttpServletResponse response) throws Exception {
		InputStream inputStream = null;
		OutputStream outputStream = null;
		Connection connection = null;
		try {
			int printFlag = PrintFlagEnum.YES.getCode();
			String loginName = HttpSessionUtil.getLoginName(servletRequest);

			// 发货单表头
			QueryWrapper<GdnHeader> gdnHeaderQueryWrapper = new QueryWrapper<GdnHeader>();
			gdnHeaderQueryWrapper.eq("SOURCE_SHIP_NOTICE", noticeNo);
			List<GdnHeader> gdnHeaderList = gdnHeaderMapper.selectList(gdnHeaderQueryWrapper);
			List<Long> gdnHeaderIdList = gdnHeaderList.stream().map(GdnHeader::getGdnHeaderId).collect(Collectors.toList());
			gdnHeaderMapper.updatePrint(null, printFlag, loginName, gdnHeaderIdList);

			// 装柜余留单表头
			QueryWrapper<GdnRemainHeader> gdnRemainHeaderQueryWrapper = new QueryWrapper<GdnRemainHeader>();
			gdnRemainHeaderQueryWrapper.in("GDN_HEADER_ID", gdnHeaderIdList);
			List<GdnRemainHeader> gdnRemainHeaderList = gdnRemainHeaderMapper.selectList(gdnRemainHeaderQueryWrapper);
			List<Integer> gdnRemainHeaderIdList = gdnRemainHeaderList.stream().map(GdnRemainHeader::getGdnRemainHeaderId).collect(Collectors.toList());
			gdnRemainHeaderMapper.updatePrint(printFlag, loginName, gdnRemainHeaderIdList);

			// 获取文件流
			ClassPathResource resource = new ClassPathResource(FileUrlString.LOADING_REMAIN_PRINT);
			inputStream = resource.getInputStream();
			JasperReport jasperReport = (JasperReport) JRLoader.loadObject(inputStream);

			// 入参
			Map<String, Object> parameters = new HashMap<String, Object>(1);
			List<ExporterInputItem> items = new ArrayList<ExporterInputItem>(gdnRemainHeaderList.size());
			connection = dataSource.getConnection();
			for (GdnRemainHeader gdnRemainHeader : gdnRemainHeaderList) {

				parameters.clear();
				parameters.put("GDN_REMAIN_HEADER_ID", gdnRemainHeader.getGdnRemainHeaderId());

				JasperPrint jasperPrint = JasperFillManager.fillReport(jasperReport, parameters, connection);

				// 设置pdf尺寸
				jasperPrint.setPageWidth(595);
				jasperPrint.setPageHeight(396);
				jasperPrint.setLeftMargin(0);
				jasperPrint.setRightMargin(0);
				jasperPrint.setTopMargin(0);
				jasperPrint.setBottomMargin(0);
				items.add(new SimpleExporterInputItem(jasperPrint));

			}



			log.info("保存文件开始");
			//将文件保存到本地服务器
			String absolutePath = fileService.saveFile(items, "余留单", "xlsx");
			//返回对应的路径
			Result result = new Result();
			result.setData(absolutePath);

			return result;
		} catch (Exception e){
			e.printStackTrace();
			throw e;
		} finally {
			if(outputStream != null) {
				outputStream.close();
			}
			if(inputStream != null) {
				inputStream.close();
			}
			if(connection!=null){
				connection.close();
			}
		}
	}

	/**
	 * 计算余留单
	 * @param noticeNo
	 * @return
	 * 余留SN放置规则
	 * 1》优先放置SN对应的拣货单的发货单明细下，不能超当前发货单明细的余留数
	 * 2》根据订单号、物料放置对应的余留信息下
	 * 3》随机
	 */
	public CalculateRemainResponse calculateRemainDoc(String noticeNo) {
		// 根据【来源单号】按【订单号、料号】分组统计出整体余留数
		List<QtyGroupPoSkuDto> datas = gdnDtlMapper.selectQtyGroupPoSku(noticeNo);
		
		// 总订单数
		BigDecimal allDataOrderQty = datas.stream().map(t -> t.getOrderQty()).reduce(BigDecimal.ZERO, BigDecimal::add);
		// 总发货数
		BigDecimal allDataShipQty = datas.stream().map(t -> t.getShipQty()).reduce(BigDecimal.ZERO, BigDecimal::add);
		// 总余留数
		BigDecimal allDataRemainQty = allDataOrderQty.subtract(allDataShipQty);
		
		// 是否已经余留过
		boolean isRemainEd = datas.stream().map(t -> t.getRemainQty()).reduce(BigDecimal.ZERO, BigDecimal::add).signum() == 1;
		if(allDataRemainQty.signum() < 1 || isRemainEd) {
			Shift.fatal("不允许余留！");
		}
		
		// 余留信息集合
		List<CalculateRemainItemResponse> calculateRemainItemList = new ArrayList<>();
		for (QtyGroupPoSkuDto one : datas) {
			// 整体余留数
			BigDecimal allRemainQty = one.getOrderQty().subtract(one.getShipQty());
			
			// 根据【来源单号、订单号、料号】查找明细
			List<GdnDtl> gdnDtlList = gdnDtlMapper.selectAllByPoSkuSourceNo(noticeNo, one.getPoNo(), one.getSkuId());
			for (GdnDtl dtl : gdnDtlList) {	// 遍历明细开始
				// 表头
				GdnHeader gdnHeader = gdnHeaderMapper.selectById(dtl.getGdnHeaderId());
				// 货品
				Sku sku = skuMapper.selectById(dtl.getSkuId());
				// 仓库
				Warehouse whs = null;
				if (dtl.getWhsId() != null) {
					whs = warehouseMapper.selectById(dtl.getWhsId());
				}
				
				// 差异数
				BigDecimal differenceQty = dtl.getOrderQty().subtract(dtl.getShipQty());
				// 未拣货数量
				BigDecimal unPickQty = dtl.getOrderQty().subtract(dtl.getPickQty());
				
				// 余留信息
				CalculateRemainItemResponse remainItem = new CalculateRemainItemResponse();
				remainItem.setGdnNo(gdnHeader.getGdnNo());		// 发货单号
				remainItem.setCntrNo(gdnHeader.getCntrNo());	// 柜号
				remainItem.setPoNo(dtl.getPoNo());				// 订单号
				remainItem.setSkuId(dtl.getSkuId());			// 物料ID
				remainItem.setSkuCode(sku.getSkuCode());		// 物料编码
				remainItem.setShipWhsId(dtl.getWhsId());		// 发出仓ID
				remainItem.setShipWhsName(whs == null ? null : whs.getWhsName());	// 发出仓NAME
				remainItem.setOrderQty(dtl.getOrderQty());		// 订单数
				remainItem.setShipQty(dtl.getShipQty());		// 发货数
				remainItem.setUnPickQty(unPickQty);             // 未拣货数量
				remainItem.setDifferenceQty(differenceQty);		// 差异数
				remainItem.setRemainQty(BigDecimal.ZERO);		// 余留数
				remainItem.setGdnDtlId(dtl.getGdnDtlId());		// 发货单明细ID
				calculateRemainItemList.add(remainItem);
				
				// 整体余留数 > 0 并且  差异数 > 0
				if (allRemainQty.signum() == 1 && differenceQty.signum() == 1) {
					BigDecimal remainQty = null;
					if (allRemainQty.compareTo(differenceQty) == 1) {
						// 整体余留数 > 差异数，余留数=当前明细的差异数
						remainQty = differenceQty;
					} else {
						// 整体余留数 <= 差异数，余留数=整体余留数
						remainQty = allRemainQty;
					}
					allRemainQty = allRemainQty.subtract(remainQty);
					
					remainItem.setRemainQty(remainQty);
					remainItem.setRemainWhsId(remainItem.getShipWhsId());
					remainItem.setRemainWhsName(remainItem.getShipWhsName());
				}
			} // 遍历明细结束
		}
		// 根据余留数量升序
		calculateRemainItemList = calculateRemainItemList.stream().sorted(Comparator.comparing(CalculateRemainItemResponse::getRemainQty)).collect(Collectors.toList());
		
		// 【当前来源通知单下所有的已发运的SN集合】
		List<String> gdnDtlGoodsSnList = gdnDtlGoodsMapper.selectBySourceNo(noticeNo);

		// 【当前来源通知单下所有的拣货的SN集合】
		List<PicktaskDtlGoodsBySourceNo> picktaskDtlGoodsList = picktaskDtlGoodsMapper.selectBySourceNo(noticeNo);

		// 【当前来源通知单下所有的余留的SN】
		List<PicktaskDtlGoodsBySourceNo> remainGoodsList = picktaskDtlGoodsList.stream().filter(g -> !gdnDtlGoodsSnList.contains(g.getGoodsSn())).collect(Collectors.toList());
		
		// 【遍历处理余留的SN】
		List<CalculateRemainItemResponse> calculateRemainGoodsItemList = new ArrayList<>();
		
		// 1》排给同一个发货单明细
		dealRemainGoodsList(1, null, remainGoodsList, calculateRemainItemList, calculateRemainGoodsItemList);

		// 2》排给相同发货单、相同料号（优先分摊给未被复制的单）
		dealRemainGoodsList(2, false, remainGoodsList, calculateRemainItemList, calculateRemainGoodsItemList);
		dealRemainGoodsList(2, null, remainGoodsList, calculateRemainItemList, calculateRemainGoodsItemList);

		// 3》随机排（优先分摊给未被复制的单）
		dealRemainGoodsList(3, false, remainGoodsList, calculateRemainItemList, calculateRemainGoodsItemList);
		dealRemainGoodsList(3, null, remainGoodsList, calculateRemainItemList, calculateRemainGoodsItemList);
		
		// 过滤掉空的余留信息，并且将余留SN的余留信息集合添加进来
		calculateRemainItemList = calculateRemainItemList.stream().filter(c -> c != null).collect(Collectors.toList());
		calculateRemainItemList.addAll(calculateRemainGoodsItemList);
		
		// 【统计运算】
		// 根据【发货单号、柜号、订单号、物料、发出仓、余留仓】统计
		Map<String, List<CalculateRemainItemResponse>> itemMap = calculateRemainItemList.stream().collect(
				Collectors.groupingBy(t -> (t.getGdnNo() + "#" + t.getCntrNo() + "#" + t.getPoNo() + "#" + 
						t.getSkuId() + "#" + t.getShipWhsId() + "#" + t.getRemainWhsId()), Collectors.toList()));
		// 余留信息集合
		List<CalculateRemainItemResponse> remainItemList = new ArrayList<>(itemMap.size());
		for (String itemKey : itemMap.keySet()) {
			CalculateRemainItemResponse item = new CalculateRemainItemResponse();
			
			List<CalculateRemainItemResponse> itemKeyList = itemMap.get(itemKey);
			BeanUtil.copyNonNull(itemKeyList.get(0), item);
			item.setOrderQty(BigDecimal.ZERO);
			item.setShipQty(BigDecimal.ZERO);
			item.setRemainQty(BigDecimal.ZERO);
			item.setDifferenceQty(BigDecimal.ZERO);

			// 已统计处理的明细
			Set<Long> dealGdnDtlIdSet = new HashSet<Long>();
			for (CalculateRemainItemResponse one : itemKeyList) {
				item.setRemainQty(item.getRemainQty().add(one.getRemainQty())); 	// 余留数
				if (!dealGdnDtlIdSet.contains(one.getGdnDtlId())) {
					item.setOrderQty(item.getOrderQty().add(one.getOrderQty())); 	// 订单数
					item.setShipQty(item.getShipQty().add(one.getShipQty())); 		// 发货数
					item.setDifferenceQty(item.getDifferenceQty().add(one.getDifferenceQty())); // 差异数
					dealGdnDtlIdSet.add(one.getGdnDtlId());
				}
			}
			// 差异数转换
			if(item.getDifferenceQty().signum() == 1) {
				item.setDifferenceQty(item.getDifferenceQty().negate());
			} else {
				item.setDifferenceQty(item.getDifferenceQty().abs());
			}
			// 设置是否余留标识
			if (item.getRemainQty().signum() == 1) {
				item.setIsRemain(CommonString.YES_CN);
			} else {
				item.setIsRemain(CommonString.NO_CN);
			}
			remainItemList.add(item);
		}
		
		// 余留信息统计结果
		CalculateRemainResponse result = new CalculateRemainResponse();
		result.setOrderQty(allDataOrderQty); 	// 总订单数
		result.setShipQty(allDataShipQty); 		// 总发货数
		result.setRemainQty(allDataRemainQty); 	// 总余留数
		result.setItemList(remainItemList);		// 余留信息集合
		return result;
	}
	
	/**
	 * 创建余留单
	 * 1、所有状态的发货单明细都可以余留
	 * 2、根据余留仓库的不同，拆分余留明细
	 * 3、余留仓库根据goods查找
	 * @param dto
	 * @param servletRequest
	 * @return
	 * @throws Exception 
	 */
	@Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
	public synchronized Result<?> createRemainDoc(CreateRemainDocRequest dto, HttpServletRequest servletRequest) throws Exception {
		String noticeNo = dto.getNoticeNo();
		String reason = dto.getReason();
		
		// 根据【来源单号】按【订单号、料号】分组统计出整体余留数
		List<QtyGroupPoSkuDto> datas = gdnDtlMapper.selectQtyGroupPoSku(noticeNo);
		
		// 余留信息集合
		List<CalculateRemainItemResponse> calculateRemainItemList = new ArrayList<>();
		for (QtyGroupPoSkuDto one : datas) {
			// 整体余留数
			BigDecimal allRemainQty = one.getOrderQty().subtract(one.getShipQty());
			
			// 根据【来源单号、订单号、料号】查找明细
			List<GdnDtl> gdnDtlList = gdnDtlMapper.selectAllByPoSkuSourceNo(noticeNo, one.getPoNo(), one.getSkuId());
			for (GdnDtl dtl : gdnDtlList) {	// 遍历明细开始
				// 差异数
				BigDecimal differenceQty = dtl.getOrderQty().subtract(dtl.getShipQty());
				// 未拣货数量
				BigDecimal unPickQty = dtl.getOrderQty().subtract(dtl.getPickQty());
				
				// 余留信息
				// 整体余留数 > 0 并且  差异数 > 0
				if (allRemainQty.signum() == 1 && differenceQty.signum() == 1) {
					BigDecimal remainQty = null;
					if (allRemainQty.compareTo(differenceQty) == 1) {
						// 整体余留数 > 差异数，余留数=当前明细的差异数
						remainQty = differenceQty;
					} else {
						// 整体余留数 <= 差异数，余留数=整体余留数
						remainQty = allRemainQty;
					}
					allRemainQty = allRemainQty.subtract(remainQty);
					
					CalculateRemainItemResponse remainItem = new CalculateRemainItemResponse();
					remainItem.setPoNo(dtl.getPoNo());						// 订单号
					remainItem.setSoNo(dtl.getSoNo());						// 销售订单
					remainItem.setSkuId(dtl.getSkuId());					// 物料id
					remainItem.setShipWhsId(dtl.getWhsId());				// 发出仓id
					remainItem.setGdnDtlId(dtl.getGdnDtlId());				// 发货单明细id
					remainItem.setGdnHeaderId(dtl.getGdnHeaderId());		// 发货单主档id
					remainItem.setUnPickQty(unPickQty);             		// 未拣货数量
					remainItem.setRemainQty(remainQty);						// 余留数
					remainItem.setRemainWhsId(remainItem.getShipWhsId());	// 余留仓id
					calculateRemainItemList.add(remainItem);
				}
			} // 遍历明细结束
		}
		// 根据余留数量升序
		calculateRemainItemList = calculateRemainItemList.stream().sorted(Comparator.comparing(CalculateRemainItemResponse::getRemainQty)).collect(Collectors.toList());
		
		// 【当前来源通知单下所有的已发运的SN集合】
		List<String> gdnDtlGoodsSnList = gdnDtlGoodsMapper.selectBySourceNo(noticeNo);
		
		// 【当前来源通知单下所有的拣货的SN集合】
		List<PicktaskDtlGoodsBySourceNo> picktaskDtlGoodsList = picktaskDtlGoodsMapper.selectBySourceNo(noticeNo);

		// 【当前来源通知单下所有的余留的SN】
		List<PicktaskDtlGoodsBySourceNo> remainGoodsList = picktaskDtlGoodsList.stream().filter(g -> !gdnDtlGoodsSnList.contains(g.getGoodsSn())).collect(Collectors.toList());
		
		// 【遍历处理余留的SN】
		List<CalculateRemainItemResponse> calculateRemainGoodsItemList = new ArrayList<>();
		
		// 1》排给同一个发货单明细
		dealRemainGoodsList(1, null, remainGoodsList, calculateRemainItemList, calculateRemainGoodsItemList);

		// 2》排给相同发货单、相同料号（优先分摊给未被复制的单）
		dealRemainGoodsList(2, false, remainGoodsList, calculateRemainItemList, calculateRemainGoodsItemList);
		dealRemainGoodsList(2, null, remainGoodsList, calculateRemainItemList, calculateRemainGoodsItemList);

		// 3》随机排（优先分摊给未被复制的单）
		dealRemainGoodsList(3, false, remainGoodsList, calculateRemainItemList, calculateRemainGoodsItemList);
		dealRemainGoodsList(3, null, remainGoodsList, calculateRemainItemList, calculateRemainGoodsItemList);

		// 过滤掉空的余留信息，并且将余留SN的余留信息集合添加进来
		calculateRemainItemList = calculateRemainItemList.stream().filter(c -> c != null).collect(Collectors.toList());
		calculateRemainItemList.addAll(calculateRemainGoodsItemList);
		
		// 【统计运算】
		int gdnRemainStatus = 1; 								// 余留单状态
		int printFlag = PrintFlagEnum.NO.code; 					// 余留单打印标识
		int waitDowmCode = GoodsStatusEnum.WAIT_DOWM.code; 		// SN状态【已下架】
		int gdnDtlCloseCode = GdnDtlStatusEnum.CLOSE.code;		// 发货单明细关闭状态
		int gdnHeaderCloseCode = GdnHeaderStatusEnum.CLOSE.code;// 发货单主档关闭状态
		
		String loginName = HttpSessionUtil.getLoginName(servletRequest);
		Date date = new Date();
		
		List<String> docNoList = new ArrayList<>();							// 余留单号集合
		UpdateGdnDtlQtyDto updateGdnDtlQtyDto = new UpdateGdnDtlQtyDto();	// 更新余留数量入参
		updateGdnDtlQtyDto.setModifyUser(loginName);
		
		Map<Long, List<CalculateRemainItemResponse>> gdnHeaderMap = calculateRemainItemList.stream().collect(Collectors.groupingBy(t -> t.getGdnHeaderId(), Collectors.toList()));
		for (Long gdnHeaderId : gdnHeaderMap.keySet()) {
			// 发货单表头
			GdnHeader gdnHeader = gdnHeaderMapper.selectById(gdnHeaderId);
			// 余留单号
			String docNo = basicSimpleCodeRuleApi.GenerateCode(REMAINNO);
			docNoList.add(docNo);
			
			// 创建余留单表头
			GdnRemainHeader gdnRemainHeader = new GdnRemainHeader();
			gdnRemainHeader.setDocNo(docNo);
			gdnRemainHeader.setGdnNo(gdnHeader.getGdnNo());
			gdnRemainHeader.setGdnHeaderId(gdnHeaderId);
			gdnRemainHeader.setCntrNo(gdnHeader.getCntrNo());
			gdnRemainHeader.setVehicleNo(gdnHeader.getVehicleNo());
			gdnRemainHeader.setReason(reason);
			gdnRemainHeader.setPrintFlag(printFlag);
			gdnRemainHeader.setStatus(gdnRemainStatus);
			gdnRemainHeader.setCreateUser(loginName);
			gdnRemainHeader.setCreateTime(date);
			gdnRemainHeaderMapper.insert(gdnRemainHeader);
			
			// 明细列表
			List<CalculateRemainItemResponse> gdnDtlList = gdnHeaderMap.get(gdnHeaderId);
			Map<String, List<CalculateRemainItemResponse>> gdnDtlMap = gdnDtlList.stream().collect(Collectors.groupingBy(
					t -> (t.getGdnDtlId() + "#" + t.getPoNo() + "#" + t.getSkuId() + "#" + t.getShipWhsId() + t.getRemainWhsId()), Collectors.toList()));
			for (String gdnDtlKey : gdnDtlMap.keySet()) {
				gdnDtlList = gdnDtlMap.get(gdnDtlKey);
				CalculateRemainItemResponse dtlItem = gdnDtlList.get(0);
				BigDecimal remainQty = gdnDtlList.stream().map(t -> t.getRemainQty()).reduce(BigDecimal.ZERO, BigDecimal::add);
				
				// 创建余留单明细
				GdnRemainDtl gdnRemainDtl = new GdnRemainDtl();
				gdnRemainDtl.setGdnRemainHeaderId(gdnRemainHeader.getGdnRemainHeaderId());
				gdnRemainDtl.setGdnDtlId(dtlItem.getGdnDtlId());
				gdnRemainDtl.setPo(dtlItem.getPoNo());
				gdnRemainDtl.setSkuId(dtlItem.getSkuId());
				gdnRemainDtl.setShipWhs(dtlItem.getShipWhsId()); 		// 发出仓
				gdnRemainDtl.setRemainWhs(dtlItem.getRemainWhsId()); 	// 余留仓
				gdnRemainDtl.setRemainQty(remainQty);					// 余留数
				gdnRemainDtl.setStatus(gdnRemainStatus);
				gdnRemainDtl.setCreateUser(loginName);
				gdnRemainDtl.setCreateTime(date);
				gdnRemainDtlMapper.insert(gdnRemainDtl);
				
				for (CalculateRemainItemResponse gdnDtl : gdnDtlList) {
					Goods goods = gdnDtl.getRemainGoods();
					if (goods == null) {
						continue;
					}
					GdnRemainDtlGoods gdnRemainDtlGoods = new GdnRemainDtlGoods();
					gdnRemainDtlGoods.setGdnRemainDtlId(gdnRemainDtl.getGdnRemainDtlId());
					gdnRemainDtlGoods.setGoodsId(goods.getGoodsId());
					gdnRemainDtlGoods.setGoodsSn(goods.getGoodsSn());
					gdnRemainDtlGoods.setQty(goods.getQty());
					gdnRemainDtlGoods.setStatus(waitDowmCode);	// 已下架
					gdnRemainDtlGoods.setCreateUser(loginName);
					gdnRemainDtlGoods.setCreateTime(date);
					gdnRemainDtlGoodsMapper.insert(gdnRemainDtlGoods);

					// 【释放拣了货还没有装柜的库存】
					updateInventoryPickTaskRemain(goods, loginName);

					// 更新SN
					String oldGoodsJson = JSON.toJSONString(goods);
					goods.setLocId(null);
					goods.setStatus(waitDowmCode); // 已下架
					goods.setModifyUser(loginName);
					goods.setModifyTime(date);
					String newGoodsJson = JSON.toJSONString(goods);
					goodsMapper.updateLocIdAndStatusById(goods);

					// 新增SN日志
					GoodsLog goodsLog = new GoodsLog();
					goodsLog.setOpCode(GoodsLogEnum.REMAIN.getCode());
					goodsLog.setReason(GoodsLogEnum.REMAIN.getEnumName());
					goodsLog.setOperator(loginName);
					goodsLog.setCreateUser(loginName);
					goodsLog.setCreateTime(date);
					goodsLog.setGoodsSn(goods.getGoodsSn());
					goodsLog.setUpdateContent("未更新的:" + oldGoodsJson + ",更新的:" + newGoodsJson);
					goodsService.insertGoodsLog(goodsLog);
				}
				
				// 更新发货单明细余留数量
				updateGdnDtlQtyDto.setGdnDtlId(gdnRemainDtl.getGdnDtlId());
				updateGdnDtlQtyDto.setRemainQty(gdnRemainDtl.getRemainQty());
				gdnDtlMapper.updateGdnDtlQty(updateGdnDtlQtyDto);
				
				// 更新发货单明细状态 = 关闭
				gdnDtlMapper.updateStatusById(gdnDtlCloseCode, loginName, gdnRemainDtl.getGdnDtlId());
			}
			
			// 更新发货单主档状态 = 关闭
			gdnHeaderMapper.updateStatusById(gdnHeaderCloseCode, loginName, gdnHeaderId);
		}
		String docNoStr = StringUtils.join(docNoList.toArray(), SeparatorString.COMMA);
//		Shift.fatal("事务回滚");
		return new Result<>("余留成功！余留单号：" + docNoStr);
	}
	
    /**
     * 拣货数量余留扣减库存
     * 
     * @param goods
     * @param loginName
     * @throws Exception 
     */
    private void updateInventoryPickTaskRemain(Goods goods, String loginName) throws Exception {
    	// 变更库位库存，【减少有库位的数量库存和分配数库存、增加没有库位的数量库存】
    	UpdateInventoryInputDto oldLocInv = new UpdateInventoryInputDto();
    	oldLocInv.setOperator(loginName);					// 更新人
    	oldLocInv.setWhsId(goods.getWhsId());				// 仓库
    	oldLocInv.setLocId(goods.getLocId());				// 库位
    	oldLocInv.setOwner(goods.getOwner());				// 货主
    	oldLocInv.setSkuId(goods.getSkuId());				// 物料
    	oldLocInv.setFactory(goods.getFactory());			// 组织
        oldLocInv.setVendorId(goods.getVendorId());			// 供应商
        oldLocInv.setLotNo(goods.getLotno());				// 批次号
        oldLocInv.setInvAttr(goods.getInvAttr());			// 库存性质
        oldLocInv.setQaStatus(goods.getQaStatus());			// QA状态
        oldLocInv.setUnit(goods.getUnit());					// 单位
        oldLocInv.setQty(goods.getQty().negate());			// 数量【-】
        oldLocInv.setAlocQty(goods.getQty().negate());		// 分配【-】
        inventoryService.updateLocInventroy(oldLocInv);
        
        UpdateInventoryInputDto newLocInv = new UpdateInventoryInputDto();
        BeanUtil.copyNonNull(oldLocInv, newLocInv);
        newLocInv.setLocId(null);							// 库位 = NULL
        newLocInv.setQty(goods.getQty());					// 数量【+】
        newLocInv.setAlocQty(BigDecimal.ZERO);				// 分配【!】
        inventoryService.updateLocInventroy(newLocInv);
        
        
        // 变更库区库存，【减少有库区的数量库存和分配数库存、增加没有库区的数量库存】
        Integer zoneId = null;
        if(goods.getLocId() != null) {
        	Location location = locationService.getLocationByLocId(goods.getLocId());
        	zoneId = location.getZoneId();
        }
        UpdateInventoryInputDto oldZoneInv = new UpdateInventoryInputDto();
        oldZoneInv.setOperator(loginName);					// 更新人
        oldZoneInv.setWhsId(goods.getWhsId());				// 仓库
		oldZoneInv.setZoneId(zoneId);						// 库区
        oldZoneInv.setOwner(goods.getOwner());				// 货主
        oldZoneInv.setSkuId(goods.getSkuId());				// 物料
        oldZoneInv.setFactory(goods.getFactory());			// 组织
        oldZoneInv.setVendorId(goods.getVendorId());		// 供应商
        oldZoneInv.setLotNo(goods.getLotno());				// 批次号
        oldZoneInv.setInvAttr(goods.getInvAttr());			// 库存性质
        oldZoneInv.setQaStatus(goods.getQaStatus());		// QA状态
        oldZoneInv.setUnit(goods.getUnit());				// 单位
        oldZoneInv.setQty(goods.getQty().negate());			// 数量【-】
        oldZoneInv.setAlocQty(goods.getQty().negate());		// 分配【-】
        inventoryService.updateZoneInventory(oldZoneInv);
        
        UpdateInventoryInputDto newZoneInv = new UpdateInventoryInputDto();
        BeanUtil.copyNonNull(oldZoneInv, newZoneInv);
        newZoneInv.setZoneId(null);							// 库区 = NULL
        newZoneInv.setQty(goods.getQty());					// 数量【+】
        newZoneInv.setAlocQty(BigDecimal.ZERO);				// 分配【!】
        inventoryService.updateZoneInventory(newZoneInv);
    }
    
	/**
	 * 处理余留的SN
	 * @param dealType	1：同发货单明细、2：同订单号并且同物料、默认：随机
	 * @param isbyCopy
	 * @param remainGoodsList
	 * @param calculateRemainItemList
	 * @param calculateRemainGoodsItemList
	 */
	private void dealRemainGoodsList(int dealType, Boolean isbyCopy,
			List<PicktaskDtlGoodsBySourceNo> remainGoodsList,
			List<CalculateRemainItemResponse> calculateRemainItemList,
			List<CalculateRemainItemResponse> calculateRemainGoodsItemList) {
		BigDecimal oneQty = new BigDecimal(1);
		for (int i = 0; i < remainGoodsList.size(); i++) {
			PicktaskDtlGoodsBySourceNo remainGoods = remainGoodsList.get(i);
			if (remainGoods == null) {
				// 当前余留SN已处理完，跳过
				continue;
			}
			Goods goods = goodsService.selectBySn(remainGoods.getGoodsSn());
			if(goods == null) {
				goods = goodsService.selectGoodsByHisSn(remainGoods.getGoodsSn()).get(0);
			}
			for (int j = 0; j < calculateRemainItemList.size(); j++) {
				CalculateRemainItemResponse remainItem = calculateRemainItemList.get(j);
				if (remainItem == null) {
					// 当前余留信息的余留数量都已分配到其他SN，跳过
					continue;
				}
				
				if (remainItem.getRemainQty().signum() == 0) {
					// 当前余留信息的余留数量等于0，无法分摊余留的SN，跳过
					continue;
				}
				
				if (remainItem.getUnPickQty().compareTo(remainItem.getRemainQty()) >= 0) {
					// 未拣货数量 >= 余留数量，无法分摊余留的SN，跳过
					continue;
				}
				
				if(isbyCopy != null && !isbyCopy.equals(remainItem.isCopy())) {
					// 入参的是否复制标识 不等于 当前余留信息的是否复制标识
					continue;
				}
				
				boolean dealFlag = false;
				switch (dealType) {
				case 1:
					// 同发货单明细
					dealFlag = remainGoods.getGdnDtlId().equals(remainItem.getGdnDtlId());
					break;
				case 2:
					// 同订单号并且同物料
					dealFlag = (goods.getPoNo().equals(remainItem.getPoNo()) 
							&& goods.getSkuId().equals(remainItem.getSkuId()));
					break;
				default:
					// 随机
					dealFlag = true;
					break;
				}
				
				if(dealFlag) {
					Warehouse whs = warehouseMapper.selectById(goods.getWhsId());
					CalculateRemainItemResponse item = new CalculateRemainItemResponse();
					BeanUtil.copyNonNull(remainItem, item);
					item.setRemainQty(oneQty);
					item.setRemainGoods(goods);
					item.setRemainWhsId(whs.getWhsId());
					item.setRemainWhsName(whs.getWhsName());
					calculateRemainGoodsItemList.add(item);
					
					remainGoodsList.set(i, null);
					
					remainItem.setCopy(true);
					remainItem.setRemainQty(remainItem.getRemainQty().subtract(oneQty));
					if (remainItem.getRemainQty().signum() == 0) {
						calculateRemainItemList.set(j, null);
					}
					break;
				}
			}
		}
	}
}